package com.cnebula.openapi.auth.service.impl;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import com.cnebula.openapi.auth.AuthOptions;
import com.cnebula.openapi.auth.exception.AccessTokenIllegalException;
import com.cnebula.openapi.auth.exception.AuthCodeUnavailableException;
import com.cnebula.openapi.auth.service.OAuthService;
import com.cnebula.openapi.auth.util.MD5Util;
import com.cnebula.openapi.auth.vo.AccessToken;
import com.cnebula.openapi.auth.vo.AuthCode;
import com.cnebula.openapi.auth.vo.User;

import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;

public class OAuthServiceImpl implements OAuthService{
	
	RedisClient redis;
	AuthOptions options;

	private static final long CODE_EXPIRE = 60 * 1000;
	
	private static final Logger log = LoggerFactory.getLogger(OAuthServiceImpl.class);


	public OAuthServiceImpl(Vertx vertx, AuthOptions options) {
		RedisOptions redisOps = new RedisOptions();
		redisOps.setHost(options.getRedisHost());
		redisOps.setPort(options.getRedisPort());
		
		redis = RedisClient.create(vertx, redisOps);
		this.options = options;
	}

	@Override
	public Future<AuthCode> createCode(String clientKey, String tenantId, User user, AccessToken accessToken) {
		Future<AuthCode> future = Future.future();
		String logTag = "OAuthServiceImpl createCode";

		Map<String, String> params = new HashMap<String, String>();
		params.put("accessToken", accessToken.token);
		params.put("clientKey", clientKey);
		params.put("tenantId", tenantId);
		params.put("userId", user.userId);
		params.put("userName", user.userName);
		params.put("random", UUID.randomUUID().toString());
		
		String code = "";
		try {
			code = MD5Util.md5(params);
		} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
			log.error(logTag,e);
			future.fail(e);
			return future;
		}
		AuthCode authCode = new AuthCode();
		authCode.code = code;
		authCode.clientKey = clientKey;
		authCode.user = user;
		authCode.tenantId = tenantId;
		authCode.accessToken = accessToken;
		redis.psetex(authCode.code, CODE_EXPIRE, authCode.toString(), r -> {
			if (r.succeeded()) {
				future.complete(authCode);
			} else {
				log.error(logTag,r.cause());
				future.fail(r.cause());
			}
		});
		return future;
	}

	@Override
	public Future<AccessToken> authorize(String code) {
		Future<AccessToken> future = Future.future();
		String logTag = "OAuthServiceImpl authorize";
		
		Future<Void> process = Future.future();
		process.compose(t -> {//验证授权码
			Future<AuthCode> f = Future.future();
			redis.get(code, h -> {
				if (h.succeeded()) {
					String str = h.result();
					if (str != null && !str.isEmpty()) {
						AuthCode result = AuthCode.build(str);
						f.complete(result);
					}else{
						f.fail(new AuthCodeUnavailableException());
					}
				} else {
					f.fail(h.cause());
				}
			});
			return f;
		}).compose(t -> {//验证成功后移除授权码
			Future<AuthCode> f = Future.future();
			redis.del(code, h->{});
			f.complete(t);
			return f;
		}).compose(t -> {//保存access_token信息
			Future<AccessToken> f = Future.future();
			AccessToken accessToken = t.accessToken;
			redis.psetex(accessToken.token , accessToken.expire, accessToken.serialized(), r -> {
				if (r.succeeded()) {
					f.complete(accessToken);
				} else {
					f.fail(r.cause());
				}
			});
			return f;
		}).setHandler(h -> {
			if(h.succeeded()){
				future.complete(h.result());
			}else{
				log.error(logTag,h.cause());
				future.fail(h.cause());
			}
		});
		process.complete();
		
		return future;
	}

	@Override
	public Future<AccessToken> checkAccessToken(String token) {
		String logTag = "OAuthServiceImpl checkAccessToken";
		
		Future<AccessToken> future = Future.future();
		
		redis.get(token, h -> {
			if (h.succeeded()) {
				String str = h.result();
				if (str != null && !str.isEmpty()) {
					AccessToken accessToken = AccessToken.deserialize(str);
					future.complete(accessToken);
				}else{
					future.fail(new AccessTokenIllegalException());
				}
			} else {
				log.error(logTag,h.cause());
				future.fail(h.cause());
			}
		});
		
		
		return future;
	}

	@Override
	public Future<AuthCode> checkCode(String code) {
		Future<AuthCode> future = Future.future();
		String logTag = "OAuthServiceImpl check AuthCode";
		
		redis.get(code, h -> {//验证授权码
			if (h.succeeded()) {
				String str = h.result();
				if (str != null && !str.isEmpty()) {
					AuthCode result = AuthCode.build(str);
					future.complete(result);
				}else{
					future.fail(new AuthCodeUnavailableException());
				}
			} else {
				log.error(logTag,h.cause());
				future.fail(h.cause());
			}
		});
		
		return future;
	}

}
